Vue 3+Vite+TypeScript+Tailwind CSS 完整開發指南
從零開始搭建一個完整的現代化 Vue 3 前端開發環境,包含所有最佳實踐工具和配置

Vue 3 + Vite + TypeScript + Tailwind CSS 完整開發指南
本文檔介紹如何從零開始搭建一個完整的現代化 Vue 3 前端開發環境,包含所有最佳實踐工具和配置。
技術棧介紹
核心框架
Vue 3 - 漸進式 JavaScript 框架,使用 Composition API
官方文檔Vite - 下一代前端構建工具,極速的開發體驗
官方文檔TypeScript - 提供類型安全的 JavaScript 超集
官方文檔
UI 框架
Tailwind CSS 4 - 實用優先的 CSS 框架,v4 採用 CSS-first 配置,無需
tailwind.config.js
官方文檔
狀態管理與路由
HTTP 客戶端
Axios - 基於 Promise 的 HTTP 客戶端
官方文檔
自動化工具
代碼質量工具
ESLint - JavaScript/TypeScript 代碼檢查工具
官方文檔Oxlint - 基於 Rust 的超高速 Linter,比 ESLint 快 50-100 倍,可與 ESLint 並存
官方文檔Oxfmt - 基於 Rust 的超高速格式化工具,比 Prettier 快 30 倍,支援 Import 排序(Beta)
官方文檔Prettier - 代碼格式化工具(穩定替代方案)
官方文檔Commitlint - Git 提交訊息規範檢查
官方文檔Husky - Git hooks 管理工具
官方文檔lint-staged - 暫存文件代碼檢查
GitHub
推薦庫
以下是一些在 Vue 3 項目中常用的推薦庫,可根據項目需求選擇安裝:
Iconify - 統一的圖標框架,支持超過 20 萬個圖標
官方文檔lodash-es - JavaScript 實用工具庫(ES 模塊版本)
官方文檔VueUse - Vue Composition API 工具集合
官方文檔dayjs - 輕量級的日期處理庫
官方文檔decimal.js - 任意精度的十進制算術庫
官方文檔vue-draggable-plus - Vue 3 拖拽組件
官方文檔vue3-lottie - Vue 3 的 Lottie 動畫組件
官方文檔
環境準備
系統要求
根據 Vue 官方推薦:
Node.js >= 20.19.0 或 >= 22.12.0
npm >= 10.0.0 或 pnpm >= 9.0.0
檢查版本
# 檢查 Node.js 版本
node --version
# 檢查 npm 版本
npm --version
# 或檢查 pnpm 版本(推薦)
pnpm --version
安裝 pnpm(推薦)
# 使用 npm 全局安裝 pnpm
npm install -g pnpm
# 或使用 Homebrew(macOS)
brew install pnpm
項目初始化
1. 使用 create-vue 創建項目
Vue 官方提供了 create-vue 腳手架工具,這是創建 Vue 3 項目的推薦方式。
# 使用 npm
npm create vue@latest
# 或使用 pnpm(推薦)
pnpm create vue@latest
執行後會出現互動式選項,建議依需求勾選:
✔ 請輸入專案名稱: my-vue-app
✔ 是否使用 TypeScript? … 是/否
✔ 是否啟用 JSX 支援? … 是/否
✔ 是否引入 Vue Router(單頁應用)? … 是/否
✔ 是否引入 Pinia(狀態管理)? … 是/否
✔ 是否引入 Vitest(單元測試)? … 是/否
✔ 是否引入 ESLint(程式碼檢查)? … 是/否
✔ 是否引入 Prettier(程式碼格式化)? … 是/否
# 額外測試特性(依版本可能出現)
◻ Oxlint(超快靜態檢查器,與 ESLint 可並存)
◻ rolldown-vite(試驗性功能)
# 其他
✔ 跳過所有範例程式碼,建立一個空白的 Vue 專案? … Yes/No
備註:選項會隨 create-vue 版本略有不同,上述為目前常見項目。若未勾選某功能(如 Router/Pinia/Vitest),可於後續依照本文對應章節補裝。
2. 安裝依賴
cd my-vue-app
# 安裝依賴
npm install
# 或使用 pnpm
pnpm install
3. 測試運行
# 使用 npm
npm run dev
# 或使用 pnpm
pnpm dev
訪問 http://localhost:5173 確認項目正常運行。
安裝與配置
1. TypeScript 配置
create-vue 已經為我們配置好了 TypeScript。項目使用了分離式的配置檔案:
tsconfig.json- 專案參考配置(主配置)tsconfig.app.json- 應用程式碼配置tsconfig.node.json- Node 工具配置(Vite、ESLint 等)tsconfig.vitest.json- 測試配置
查看 tsconfig.json
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.vitest.json"
}
]
}
查看 tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}
路徑別名 @ 已經默認配置好了,可以直接使用。
查看 vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
Vite 配置已經包含了路徑別名和 Vue DevTools 插件,無需額外配置。
配置 Base 路徑
如果應用部署在非根路徑下(例如 localhost/web/),需要在 vite.config.ts 中配置 base 選項:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
base: '/web/', // 配置應用基礎路徑
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
注意事項:
base必須以/開頭和結尾(例如/web/)如果使用環境變數,可以通過
import.meta.env.BASE_URL訪問Vue Router 的
createWebHistory會自動使用import.meta.env.BASE_URL,無需額外配置開發環境和生產環境可以使用不同的 base 路徑,通過環境變數控制
2. Tailwind CSS 配置
推薦使用 Tailwind CSS 4:v4 採用全新 CSS-first 架構,無需
tailwind.config.js,透過 Vite 插件整合,設定更簡單。需要現代瀏覽器(Safari 16.4+、Chrome 111+、Firefox 128+)。
安裝依賴
# 使用 npm
npm install -D tailwindcss @tailwindcss/vite
# 或使用 pnpm
pnpm add -D tailwindcss @tailwindcss/vite
注意:v4 不再需要
postcss和autoprefixer。
配置 vite.config.ts
在 Vite 配置中加入 Tailwind CSS 插件:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
tailwindcss(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
創建 src/assets/styles/tailwind.css
v4 使用 @import 取代舊版的 @tailwind 指令,並透過 @theme 定義自訂主題變數:
@import "tailwindcss";
/* 自訂主題變數(取代 tailwind.config.js 的 theme.extend) */
@theme {
--color-primary-50: #f0f9ff;
--color-primary-100: #e0f2fe;
--color-primary-200: #bae6fd;
--color-primary-300: #7dd3fc;
--color-primary-400: #38bdf8;
--color-primary-500: #0ea5e9;
--color-primary-600: #0284c7;
--color-primary-700: #0369a1;
--color-primary-800: #075985;
--color-primary-900: #0c4a6e;
--color-primary-950: #082f49;
}
/* 自定義全局樣式 */
@layer base {
body {
@apply bg-gray-50 text-gray-900;
}
}
/* v4 使用 @utility 取代 @layer components */
@utility btn-primary {
@apply bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-colors;
}
@utility btn-secondary {
@apply bg-gray-200 text-gray-800 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors;
}
@utility card {
@apply bg-white rounded-lg shadow-md p-6;
}
v3 → v4 重要變更:
@tailwind base/components/utilities→@import "tailwindcss"
@layer utilities/components→@utility
tailwind.config.js的theme.extend→ CSS@theme變數
shadow-sm→shadow-xs、shadow→shadow-sm(陰影命名調整)
!important修飾符:!flex→flex!
在 src/main.ts 中引入
import { createApp } from 'vue'
import './assets/styles/tailwind.css'
import App from './App.vue'
createApp(App).mount('#app')
從 v3 遷移
如需從 v3 自動遷移:
npx @tailwindcss/upgrade
3. Vue Router 配置
若在 create-vue 勾選了 Router,本節可直接使用既有檔案;
若未勾選,請先安裝並初始化:plain12345# 使用 npm npm install vue-router # 或 pnpm pnpm add vue-router
查看 src/router/index.ts
系統已自動生成路由配置文件,我們可以在此基礎上進行擴展:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import type { App } from 'vue'
// 路由配置
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('@/views/HomeView.vue'),
meta: {
title: '首頁'
}
},
{
path: '/about',
name: 'About',
component: () => import('@/views/AboutView.vue'),
meta: {
title: '關於'
}
}
]
// 創建路由實例
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
// 全局前置守衛
router.beforeEach((to, from, next) => {
// 設置頁面標題
document.title = (to.meta.title as string) || 'Vue App'
// 檢查是否需要登入
if (to.meta.requiresAuth) {
// 這裡可以檢查登入狀態
const isAuthenticated = localStorage.getItem('token')
if (!isAuthenticated) {
next({ name: 'Home' })
return
}
}
next()
})
// 全局後置鉤子
router.afterEach((to, from) => {
// 可以在這裡做一些統計或日誌記錄
console.log(`路由從 ${from.path} 到 ${to.path}`)
})
// 導出安裝函數
export function setupRouter(app: App) {
app.use(router)
}
export default router
更新 src/main.ts
import { createApp } from 'vue'
import './assets/styles/tailwind.css'
import App from './App.vue'
import { setupRouter } from './router'
const app = createApp(App)
// 配置路由
setupRouter(app)
app.mount('#app')
創建視圖文件
創建 src/views/HomeView.vue:
<template>
<div class="container mx-auto px-4 py-8">
<h1 class="text-4xl font-bold text-center mb-8">歡迎使用 Vue 3</h1>
<div class="card max-w-2xl mx-auto">
<p class="text-lg mb-4">這是一個使用以下技術構建的現代化 Vue 3 應用:</p>
<ul class="list-disc list-inside space-y-2">
<li>Vue 3 + Composition API</li>
<li>Vite 5 - 極速構建工具</li>
<li>TypeScript - 類型安全</li>
<li>Tailwind CSS 4 - 實用優先的 CSS</li>
<li>Pinia - 狀態管理</li>
<li>Vue Router - 路由管理</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
// 使用 Composition API
</script>
更新 src/App.vue
<template>
<div id="app">
<nav class="bg-white shadow-lg">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<router-link to="/" class="text-xl font-bold text-primary-600">
Vue App
</router-link>
<div class="flex gap-4">
<router-link
to="/"
class="text-gray-700 hover:text-primary-600 transition-colors"
active-class="text-primary-600 font-semibold"
>
首頁
</router-link>
<router-link
to="/about"
class="text-gray-700 hover:text-primary-600 transition-colors"
active-class="text-primary-600 font-semibold"
>
關於
</router-link>
</div>
</div>
</div>
</nav>
<main class="min-h-screen">
<router-view />
</main>
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 Vue App. All rights reserved.</p>
</div>
</footer>
</div>
</template>
<script setup lang="ts">
</script>
4. Pinia 狀態管理
若在 create-vue 勾選了 Pinia,專案已可直接使用;
若未勾選,請先安裝:plain12345# 使用 npm npm install pinia # 或 pnpm pnpm add pinia
如果需要持久化功能,可以安裝插件:
# 使用 npm
npm install pinia-plugin-persistedstate
# 或使用 pnpm
pnpm add pinia-plugin-persistedstate
修改 src/main.ts 添加持久化插件
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.use(router)
app.mount('#app')
創建 src/stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 定義用戶接口
interface User {
id: number
name: string
email: string
avatar?: string
}
// 使用 Composition API 風格定義 Store
export const useUserStore = defineStore('user', () => {
// State
const user = ref<User | null>(null)
const token = ref<string>('')
// Getters
const isAuthenticated = computed(() => !!token.value)
const userName = computed(() => user.value?.name || '訪客')
// Actions
function login(userData: User, authToken: string) {
user.value = userData
token.value = authToken
}
function logout() {
user.value = null
token.value = ''
}
return {
user,
token,
isAuthenticated,
userName,
login,
logout
}
}, {
// 配置持久化
persist: {
key: 'user-store',
storage: localStorage,
paths: ['user', 'token']
}
})
5. Axios 配置
安裝依賴
# 使用 npm
npm install axios
# 或使用 pnpm
pnpm add axios
創建 src/utils/request.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { useUserStore } from '@/stores/user'
// API 基礎配置
const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'
const timeout = 15000
// 創建 axios 實例
const service: AxiosInstance = axios.create({
baseURL,
timeout,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
// 請求攔截器
service.interceptors.request.use(
(config) => {
const userStore = useUserStore()
// 添加 token
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
},
(error: AxiosError) => {
console.error('請求錯誤:', error)
return Promise.reject(error)
}
)
// 響應攔截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
// 根據實際後端接口調整
if (res.code !== 200 && res.code !== 0) {
console.error('接口錯誤:', res.message)
// 401: 未授權
if (res.code === 401) {
const userStore = useUserStore()
userStore.logout()
window.location.href = '/login'
}
return Promise.reject(new Error(res.message || '請求失敗'))
}
return res
},
(error: AxiosError) => {
let message = '請求失敗'
if (error.response) {
switch (error.response.status) {
case 400:
message = '請求參數錯誤'
break
case 401:
message = '未授權,請重新登入'
const userStore = useUserStore()
userStore.logout()
window.location.href = '/login'
break
case 403:
message = '拒絕訪問'
break
case 404:
message = '請求資源不存在'
break
case 500:
message = '服務器內部錯誤'
break
default:
message = `連接錯誤 ${error.response.status}`
}
} else if (error.message) {
if (error.message.includes('timeout')) {
message = '請求超時'
} else if (error.message.includes('Network Error')) {
message = '網絡錯誤'
}
}
console.error('響應錯誤:', message)
return Promise.reject(error)
}
)
// 導出 axios 實例
export default service
// 封裝常用方法
export const request = {
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.get(url, config)
},
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.post(url, data, config)
},
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.put(url, data, config)
},
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.delete(url, config)
},
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.patch(url, data, config)
}
}
創建 src/api/user.ts
import { request } from '@/utils/request'
// 定義接口返回類型
export interface LoginParams {
username: string
password: string
}
export interface LoginResponse {
token: string
user: {
id: number
name: string
email: string
}
}
export interface UserInfo {
id: number
name: string
email: string
}
// 用戶相關 API
export const userApi = {
// 登入
login(data: LoginParams) {
return request.post<LoginResponse>('/auth/login', data)
},
// 獲取用戶信息
getUserInfo() {
return request.get<UserInfo>('/user/info')
}
}
創建 src/api/index.ts
// 統一導出所有 API
export * from './user'
// 如果有其他模塊的 API,可以繼續添加
// export * from './product'
// export * from './order'
API 使用示例
創建 src/composables/useAuth.ts:
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { userApi, type LoginParams } from '@/api'
export function useAuth() {
const router = useRouter()
const userStore = useUserStore()
const loading = ref(false)
const error = ref<string | null>(null)
// 登入
async function login(params: LoginParams) {
loading.value = true
error.value = null
try {
const response = await userApi.login(params)
userStore.login(response.user, response.token)
router.push('/')
return true
} catch (err: any) {
error.value = err.message || '登入失敗'
return false
} finally {
loading.value = false
}
}
// 獲取用戶信息
async function fetchUserInfo() {
loading.value = true
error.value = null
try {
const userInfo = await userApi.getUserInfo()
userStore.login(userInfo, userStore.token)
return userInfo
} catch (err: any) {
error.value = err.message || '獲取用戶信息失敗'
return null
} finally {
loading.value = false
}
}
return {
loading,
error,
login,
fetchUserInfo
}
}
環境變數配置
創建 .env 文件:
# 應用配置
VITE_APP_TITLE=Vue App
VITE_APP_VERSION=1.0.0
# API 配置
VITE_API_BASE_URL=http://localhost:3000/api
# 其他配置
VITE_USE_MOCK=false
創建 .env.development 文件:
# 開發環境
VITE_API_BASE_URL=http://localhost:3000/api
VITE_USE_MOCK=true
創建 .env.production 文件:
# 生產環境
VITE_API_BASE_URL=https://api.example.com
VITE_USE_MOCK=false
6. 自動載入組件配置(可選)
這是一個可選的優化配置,可以自動導入 Vue API 和自動註冊組件。
安裝依賴
# 使用 npm
npm install -D unplugin-auto-import unplugin-vue-components
# 或使用 pnpm
pnpm add -D unplugin-auto-import unplugin-vue-components
更新 vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
// https://vite.dev/config/
export default defineConfig({
// base: '/web/', // 如果部署在非根路徑,取消註釋並設置對應路徑
plugins: [
vue(),
vueDevTools(),
tailwindcss(),
// 自動導入 API
AutoImport({
// 導入目標
imports: [
'vue',
'vue-router',
'pinia',
{
'axios': [
['default', 'axios']
]
}
],
// 自動導入的目錄
dirs: [
'src/composables/**',
'src/stores/**',
'src/utils/**'
],
// 生成的類型聲明文件位置
dts: 'src/auto-imports.d.ts',
// ESLint 支持
eslintrc: {
enabled: true,
filepath: './.eslintrc-auto-import.json',
globalsPropValue: true
}
}),
// 自動註冊組件
Components({
// 要搜索組件的目錄
dirs: ['src/components'],
// 組件的有效文件擴展名
extensions: ['vue'],
// 搜索子目錄
deep: true,
// 生成的類型聲明文件位置
dts: 'src/components.d.ts'
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
port: 5173,
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
更新 .gitignore
將自動生成的文件加入 .gitignore:
# 自動生成的類型聲明文件
src/auto-imports.d.ts
src/components.d.ts
.eslintrc-auto-import.json
創建組件示例
創建 src/components/HelloWorld.vue:
<template>
<div class="card">
<h2 class="text-2xl font-bold mb-4">{{ title }}</h2>
<p class="text-gray-600 mb-4">{{ message }}</p>
<button @click="handleClick" class="btn-primary">
點擊次數: {{ clickCount }}
</button>
</div>
</template>
<script setup lang="ts">
// 不需要導入 ref,已經自動導入
interface Props {
title?: string
message?: string
}
withDefaults(defineProps<Props>(), {
title: 'Hello World',
message: '這是一個自動註冊的組件'
})
const clickCount = ref(0)
function handleClick() {
clickCount.value++
}
</script>
在任何 Vue 文件中使用,無需導入:
<template>
<HelloWorld title="歡迎" message="組件已自動註冊" />
</template>
<script setup lang="ts">
// 無需導入 ref, computed 等 API,已自動導入
</script>
7. ESLint + Oxlint 配置
create-vue 已經為我們配置好了 ESLint 9(使用新的 flat config 格式)。現在推薦搭配 Oxlint 一起使用,作為快速的第一道 lint 防線。
若在建立專案時沒有勾選 ESLint/Prettier,可後補安裝:
plain123456# 使用 npm npm install -D eslint eslint-plugin-vue @vue/eslint-config-typescript @vue/eslint-config-prettier # 或 pnpm pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript @vue/eslint-config-prettier
安裝 Oxlint
# 使用 npm
npm install -D oxlint eslint-plugin-oxlint
# 或使用 pnpm
pnpm add -D oxlint eslint-plugin-oxlint
eslint-plugin-oxlint 會自動關閉 ESLint 中與 Oxlint 重疊的規則,避免重複。
配置 oxlint.json
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/utils/schema.json",
"rules": {
"no-unused-vars": "warn",
"no-console": "warn"
},
"ignorePatterns": ["dist", "node_modules", "*.d.ts"]
}
更新 eslint.config.ts(整合 Oxlint)
查看 eslint.config.ts
系統已自動生成 ESLint 配置文件,使用了新的 flat config 格式。加入 oxlint 整合後:
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import oxlint from 'eslint-plugin-oxlint'
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
},
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],
},
// 關閉與 Oxlint 重疊的 ESLint 規則
oxlint.configs['flat/recommended'],
skipFormatting,
)
如果使用了自動導入插件
需要添加自動導入的 ESLint 配置:
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import autoImportGlobals from './.eslintrc-auto-import.json' with { type: 'json' }
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
},
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],
},
// 添加自動導入的全局變數
{
languageOptions: {
globals: autoImportGlobals.globals
}
},
skipFormatting,
)
自定義規則
如果需要自定義規則,可以在配置文件末尾添加:
export default defineConfigWithVueTs(
// ... 其他配置
// 自定義規則
{
rules: {
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
}
}
)
package.json 中的腳本
系統已經配置好了 lint 腳本:
{
"scripts": {
"lint": "eslint . --fix --cache"
}
}
8. 格式化工具配置
方案一:Oxfmt(推薦嘗試,Beta 版)
Oxfmt 是基於 Rust 的格式化工具,比 Prettier 快約 30 倍,內建 Import 排序、Tailwind CSS class 排序等功能。目前為 Beta 版本,已通過 100% Prettier 的 JS/TS 一致性測試。
注意:Oxfmt 目前對 Vue SFC(
.vue)的支援仍在開發中,若需格式化.vue文件建議搭配 Prettier。
安裝
# 使用 npm
npm install -D oxfmt
# 或使用 pnpm
pnpm add -D oxfmt
package.json 腳本
{
"scripts": {
"fmt": "oxfmt",
"fmt:check": "oxfmt --check"
}
}
方案二:Prettier(穩定方案)
create-vue 已經為我們配置好了 Prettier,無需額外安裝。
查看 .prettierrc.json
系統已自動生成 Prettier 配置文件:
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}
自定義配置(可選)
如果需要調整配置,可以修改 .prettierrc.json:
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"vueIndentScriptAndStyle": false
}
創建 .prettierignore
node_modules
dist
dist-ssr
coverage
*.d.ts
pnpm-lock.yaml
package-lock.json
package.json 中的腳本
系統已經配置好了 format 腳本:
{
"scripts": {
"format": "prettier --write src/"
}
}
9. Git Hooks 配置(可選)
這是一個可選的配置,用於在 Git 提交時自動檢查和格式化代碼。
安裝依賴
# 使用 npm
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
# 或使用 pnpm
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
初始化 Husky
# 使用 npm
npx husky init
# 或使用 pnpm
pnpm exec husky init
創建 commitlint.config.js
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修復 bug
'docs', // 文檔更新
'style', // 代碼格式(不影響代碼運行的變動)
'refactor', // 重構(既不是新增功能,也不是修復 bug 的代碼變動)
'perf', // 性能優化
'test', // 增加測試
'chore', // 構建過程或輔助工具的變動
'revert', // 回滾
'build' // 構建系統或外部依賴項的更改
]
],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'scope-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 100]
}
}
配置 Git Hooks
編輯 .husky/pre-commit:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 使用 npm
npm run lint-staged
# 或使用 pnpm
# pnpm lint-staged
創建 .husky/commit-msg:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 使用 npm
npx --no-install commitlint --edit $1
# 或使用 pnpm
# pnpm exec commitlint --edit $1
配置 lint-staged
在 package.json 中添加:
{
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less,html,json,md}": [
"prettier --write"
]
}
}
完整的 package.json 示例
{
"name": "my-vue-app",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "oxlint && eslint . --fix --cache",
"format": "prettier --write src/",
"prepare": "husky"
},
"dependencies": {
"vue": "^3.5.22",
"vue-router": "^4.6.3",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.0.0",
"axios": "^1.9.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/jsdom": "^27.0.0",
"@types/node": "^22.18.11",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/eslint-plugin": "^1.3.23",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.37.0",
"eslint-plugin-vue": "~10.5.0",
"jsdom": "^27.0.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"typescript": "~5.9.0",
"vite": "^7.1.11",
"vite-plugin-vue-devtools": "^8.0.3",
"vitest": "^3.2.4",
"vue-tsc": "^3.1.1",
"tailwindcss": "^4.1.0",
"@tailwindcss/vite": "^4.1.0",
"oxlint": "^1.0.0",
"eslint-plugin-oxlint": "^1.0.0",
"unplugin-auto-import": "^19.0.0",
"unplugin-vue-components": "^28.0.0",
"husky": "^9.0.0",
"lint-staged": "^15.2.0",
"@commitlint/cli": "^19.0.0",
"@commitlint/config-conventional": "^19.0.0"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": [
"oxlint --fix",
"eslint --fix",
"prettier --write"
],
"*.{css,scss,json,md}": [
"prettier --write"
]
}
}
Git 提交訊息規範
提交訊息格式:
<type>(<scope>): <subject>
<body>
<footer>
示例:
# 新功能
git commit -m "feat(user): 新增用戶登入功能"
# 修復 bug
git commit -m "fix(router): 修復路由導航錯誤"
# 文檔更新
git commit -m "docs(readme): 更新安裝說明"
# 代碼格式調整
git commit -m "style(app): 格式化代碼"
# 重構
git commit -m "refactor(store): 重構用戶狀態管理"
# 性能優化
git commit -m "perf(list): 優化列表渲染性能"
# 測試
git commit -m "test(user): 新增用戶模塊測試"
# 構建相關
git commit -m "chore(deps): 更新依賴版本"
Type 說明:
feat: 新功能fix: 修復 bugdocs: 文檔更新style: 代碼格式調整(不影響功能)refactor: 代碼重構perf: 性能優化test: 測試相關chore: 構建工具或輔助工具的變動revert: 回滾提交build: 構建系統或外部依賴的更改
項目結構
使用 create-vue 生成的項目結構,並添加額外配置後:
my-vue-app/
├── .husky/ # Git hooks(可選)
│ ├── _/
│ ├── commit-msg
│ └── pre-commit
├── .vscode/ # VS Code 配置
│ └── extensions.json
├── public/ # 靜態資源
│ └── favicon.ico
├── src/
│ ├── api/ # API 接口(自行創建)
│ │ ├── index.ts
│ │ └── user.ts
│ ├── assets/ # 資源文件
│ │ ├── base.css
│ │ ├── main.css
│ │ └── logo.svg
│ ├── components/ # 全局組件
│ │ ├── __tests__/ # 組件測試
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ └── WelcomeItem.vue
│ ├── composables/ # 組合式函數(自行創建)
│ │ └── useAuth.ts
│ ├── router/ # 路由配置
│ │ └── index.ts
│ ├── stores/ # Pinia stores
│ │ ├── counter.ts
│ │ └── user.ts # 自行創建
│ ├── utils/ # 工具函數(自行創建)
│ │ └── request.ts
│ ├── views/ # 頁面組件
│ │ ├── HomeView.vue
│ │ └── AboutView.vue
│ ├── App.vue # 根組件
│ ├── main.ts # 入口文件
│ ├── auto-imports.d.ts # 自動生成(如果使用自動導入)
│ └── components.d.ts # 自動生成(如果使用自動導入)
├── .env # 環境變數(自行創建)
├── .env.development # 開發環境變數(自行創建)
├── .env.production # 生產環境變數(自行創建)
├── .editorconfig # 編輯器配置
├── .eslintrc-auto-import.json # 自動生成(如果使用自動導入)
├── .gitattributes # Git 屬性
├── .gitignore # Git 忽略
├── .prettierrc.json # Prettier 配置
├── commitlint.config.js # Commitlint 配置(可選)
├── env.d.ts # 環境變數類型定義
├── eslint.config.ts # ESLint 配置(flat config)
├── index.html # HTML 入口
├── oxlint.json # Oxlint 配置(可選)
├── package.json # 項目配置
├── README.md # 項目說明
├── tsconfig.app.json # 應用 TypeScript 配置
├── tsconfig.json # TypeScript 主配置
├── tsconfig.node.json # Node TypeScript 配置
├── tsconfig.vitest.json # Vitest TypeScript 配置
├── vite.config.ts # Vite 配置
└── vitest.config.ts # Vitest 配置
使用指南
開發流程
1. 啟動開發服務器
# 使用 npm
npm run dev
# 或使用 pnpm
pnpm dev
訪問 http://localhost:5173
2. 創建新頁面
# 創建新的視圖組件
touch src/views/AboutView.vue
<template>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">關於頁面</h1>
<div class="card">
<p class="text-gray-600">這是關於頁面的內容。</p>
</div>
</div>
</template>
<script setup lang="ts">
// 使用自動導入的 API,無需手動 import
</script>
在路由中註冊:
// src/router/index.ts
{
path: '/about',
name: 'About',
component: () => import('@/views/AboutView.vue'),
meta: {
title: '關於'
}
}
3. 創建 Store
touch src/stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
interface User {
id: number
name: string
email: string
}
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const token = ref<string>('')
const isAuthenticated = computed(() => !!token.value)
function login(userData: User, authToken: string) {
user.value = userData
token.value = authToken
}
function logout() {
user.value = null
token.value = ''
}
return {
user,
token,
isAuthenticated,
login,
logout
}
}, {
persist: {
key: 'user-store',
storage: localStorage
}
})
4. 創建 API
touch src/api/user.ts
import { request } from '@/utils/request'
export interface LoginParams {
username: string
password: string
}
export interface LoginResponse {
token: string
user: {
id: number
name: string
email: string
}
}
export const userApi = {
login(data: LoginParams) {
return request.post<LoginResponse>('/auth/login', data)
},
getUserInfo() {
return request.get('/user/info')
}
}
代碼檢查與格式化
檢查代碼
# 運行 Oxlint(快速第一道檢查)
npx oxlint .
# 或 pnpm exec oxlint .
# 運行 ESLint 檢查(含修復)
npm run lint
# 或 pnpm lint
# 運行 Prettier 格式化
npm run format
# 或 pnpm format
提交代碼
# 添加文件到暫存區
git add .
# 提交(會自動運行 lint-staged 和 commitlint)
git commit -m "feat(product): 新增產品列表頁面"
# 推送
git push
構建與部署
構建生產版本
# 構建
npm run build
# 或 pnpm build
# 構建產物在 dist/ 目錄
預覽生產構建
npm run preview
# 或 pnpm preview
部署到 Nginx
server {
listen 80;
server_name example.com;
root /var/www/my-vue-app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
最佳實踐
1. 組件設計
單一職責原則
<!-- ❌ 不好:組件做太多事情 -->
<template>
<div>
<header>...</header>
<nav>...</nav>
<main>...</main>
<footer>...</footer>
</div>
</template>
<!-- ✅ 好:拆分成多個組件 -->
<template>
<div>
<AppHeader />
<AppNav />
<main>
<slot />
</main>
<AppFooter />
</div>
</template>
Props 驗證
// ❌ 不好:沒有類型定義
const props = defineProps({
title: String,
count: Number
})
// ✅ 好:使用 TypeScript 接口
interface Props {
title: string
count?: number
items: Item[]
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
2. Composables 設計
可復用邏輯
// ✅ 好:創建可復用的 composable
export function useLocalStorage<T>(key: string, initialValue: T) {
const storedValue = ref<T>(initialValue)
// 從 localStorage 讀取
function read() {
try {
const item = window.localStorage.getItem(key)
storedValue.value = item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(error)
storedValue.value = initialValue
}
}
// 寫入 localStorage
function write(value: T) {
try {
storedValue.value = value
window.localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
console.error(error)
}
}
// 刪除
function remove() {
try {
window.localStorage.removeItem(key)
storedValue.value = initialValue
} catch (error) {
console.error(error)
}
}
// 初始化時讀取
read()
return {
value: storedValue,
write,
remove
}
}
3. 狀態管理
Store 組織
// ✅ 好:按功能模塊組織 store
stores/
├── index.ts # 導出所有 store
├── user.ts # 用戶相關
├── cart.ts # 購物車
├── product.ts # 產品
└── app.ts # 應用配置
避免過度使用 Store
// ❌ 不好:把所有狀態都放到 store
const appStore = useAppStore()
const localCount = computed(() => appStore.tempCount)
// ✅ 好:局部狀態使用 ref/reactive
const localCount = ref(0)
4. API 請求
統一錯誤處理
// ✅ 好:在 composable 中統一處理錯誤
export function useApi() {
const loading = ref(false)
const error = ref<string | null>(null)
async function request<T>(
apiCall: () => Promise<T>,
errorMessage = '請求失敗'
): Promise<T | null> {
loading.value = true
error.value = null
try {
const result = await apiCall()
return result
} catch (err: any) {
error.value = err.message || errorMessage
return null
} finally {
loading.value = false
}
}
return {
loading,
error,
request
}
}
5. 性能優化
使用 v-memo
<template>
<!-- 當 item 沒變化時,跳過更新 -->
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">
{{ item.name }}
</div>
</template>
懶加載路由
// ✅ 好:路由懶加載
{
path: '/products',
component: () => import('@/views/ProductView.vue')
}
使用 shallowRef
// 對於大型數據結構,使用 shallowRef
const largeData = shallowRef({
// 大量數據...
})
6. TypeScript 使用
定義清晰的類型
// ✅ 好:定義清晰的接口
interface User {
id: number
name: string
email: string
avatar?: string
role: 'admin' | 'user' | 'guest'
createdAt: string
updatedAt: string
}
// 使用工具類型
type UserUpdateData = Partial<Pick<User, 'name' | 'email' | 'avatar'>>
7. Tailwind CSS 使用
提取重複的樣式
/* ✅ 好:在 tailwind.css 中定義常用工具類(v4 使用 @utility) */
@utility btn {
@apply px-4 py-2 rounded-lg font-medium transition-colors;
}
@utility btn-primary {
@apply bg-primary-600 text-white hover:bg-primary-700;
}
@utility card {
@apply bg-white rounded-lg shadow-md p-6;
}
@utility input {
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500;
}
8. Git 提交規範
# ✅ 好:清晰的提交訊息
git commit -m "feat(auth): 新增用戶登入功能"
git commit -m "fix(router): 修復路由導航守衛邏輯"
git commit -m "docs(readme): 更新安裝說明"
# ❌ 不好:模糊的提交訊息
git commit -m "update"
git commit -m "fix bug"
git commit -m "修改"
快速命令參考
# 安裝依賴
npm install
# 或 pnpm install
# 啟動開發服務器
npm run dev
# 或 pnpm dev
# 構建生產版本
npm run build
# 或 pnpm build
# 預覽生產構建
npm run preview
# 或 pnpm preview
# 代碼檢查(Oxlint 快速檢查)
npx oxlint .
# 代碼檢查(ESLint 完整檢查 + 修復)
npm run lint
# 或 pnpm lint
# 代碼格式化
npm run format
# 或 pnpm format
# TypeScript 類型檢查
npm run type-check
# 或 pnpm type-check
# 運行單元測試
npm run test:unit
# 或 pnpm test:unit
# 清除緩存 (npm)
rm -rf node_modules .vite package-lock.json
npm install
# 清除緩存 (pnpm)
rm -rf node_modules .vite pnpm-lock.yaml
pnpm install
